﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
using System.Threading;

namespace Stats_Finder
{
	class Program
	{
		[MTAThread]
		static void Main(string[] args)
		{
			try
			{
				string inputFilename;
				string outputFilename;
				if (args.Length == 0)
				{
					Console.WriteLine("Please specify an image file to process");
					return;
				}
				inputFilename = args[0];
				if (!File.Exists(args[0]))
				{
					Console.WriteLine("File Not Found: {0}", args[0]);
					return;
				}
				if (args.Length > 1) outputFilename = args[1];
				Console.WriteLine("Getting pixel matrix of {0}", inputFilename);
				var start = DateTime.Now;
				PixelMatrix pm = StatsFinder.GetPixelMatrix(inputFilename);
				var end = DateTime.Now;
				var duration = end - start;
				Console.WriteLine("Process took {0}", duration);
				//return;
				Console.WriteLine("Getting Histogram");
				int[] hist = StatsFinder.GetHistogram(pm);
				Console.WriteLine("Finding Lines");
				List<TextLine> textLines = StatsFinder.GetTextLines(pm, hist);
				Console.WriteLine("Found {0} Text Lines", textLines.Count);
				Console.WriteLine("Line(s) analysis:");
				for (int i = 0; i < textLines.Count; i++)
				{
					Console.WriteLine("Line #{0:D2}:", i + 1);
					StatsFinder.PrintLineStats(pm, hist, textLines[i]);					
				}
			}
			catch (Exception ex) { Console.WriteLine("Exception Occurred: {0}", ex.Message); }
			finally { Console.Write("Press ENTER to exit ..."); Console.ReadLine(); }			
		}		
	}
	public class TextLine
	{
		public int Length;
		public int Start, End;
		public int Height { get { return End - Start; } }
		public TextLine(int start, int end)
		{
			Start = start;
			End = end;
		}
	}
	public class StatsFinder
	{
		static double Threshold = Color.Gray.GetBrightness();

		internal static PixelMatrix GetPixelMatrix(string filename)
		{
			
			Bitmap b = new Bitmap(filename);
			int pixels = b.Width * b.Height;
						
			PixelMatrix pm = new PixelMatrix(b.Width, b.Height);
			var width = b.Width;
			var height = b.Height;
			var format = b.PixelFormat;
			for (int x = 0; x < b.Width; x++) for (int y = 0; y < b.Height; y++) 
			{
				var p = b.GetPixel(x, y);
				if (p.GetBrightness() < Threshold) { pm[x, y] = true; }
			}
			
			return pm;
		}		

		internal static int[] GetHistogram(PixelMatrix pm)
		{
			int[] hist = new int[pm.Height];
			for (int y = 0; y < pm.Height; y++)
			{ hist[y] = 0; for (int x = 0; x < pm.Width; x++) if (pm[x, y]) hist[y]++; }

			return hist;
		}

		internal static List<TextLine> GetTextLines(PixelMatrix pm,int[] hist)
		{
			bool inLine = false;
			List<TextLine> lines = new List<TextLine>();
			TextLine tl = new TextLine(0, 0);

			for (int y = 0; y < hist.Length; y++)
			{				
				if (hist[y] == 0) 
				{
					if (!inLine) continue;
					inLine = false;
					tl.End = y - 1;
					lines.Add(tl);					
				}
				else if (hist[y] > 0 && !inLine)
				{
					inLine = true;
					tl = new TextLine(y,0);
				}
				if (y + 1 == hist.Length && inLine) { tl.End = y; lines.Add(tl); }
			}
			foreach (TextLine textLine in lines)
			{
				bool ended = false;
				textLine.Length = pm.Width;

				for(int x=0;x<pm.Width && !ended;x++,textLine.Length--)
					for (int y = textLine.Start; y <= textLine.End; y++) if (pm[x, y]) { ended = true; break; }
				ended = false;
				if (textLine.Length == 0) continue;
				for (int x = pm.Width-1; x >= 0 && !ended; x--, textLine.Length--)
					for (int y = textLine.Start; y <= textLine.End; y++) if (pm[x, y]) { ended = true; break; }
			}

			return lines;
		}

		internal static void PrintLineStats(PixelMatrix pm, int[] histogram, TextLine textLine)
		{
			//int numberOfLines = 50;
			//int lineLength = 75;
			// get peaks
			int[] peaks = GetPeaks(histogram, textLine, true);
			Console.WriteLine("\tFound {0} peak(s)", peaks.Length);
						
			// get transitions for each peak and the valley between
			for (int i = 0; i < peaks.Length; i++) { 
				Console.WriteLine("\t\tPeak #{0:D2}:", i + 1); 
				int trans=Transitions(pm, peaks[i]);
				Console.WriteLine("\t\t\t{0} Transitions on line of peak", trans);
				Console.WriteLine("\t\t\t{0:F2} Transitions/100 Pixels", 100.0*(double)trans / (double)textLine.Length);
				var peakHeight = PeakWidth(histogram, peaks[i]);
				Console.WriteLine("\t\t\t{0}={1:F2}% Peak Height", peakHeight, (double)peakHeight/(double)textLine.Height*100.0);
			}
			if (peaks.Length > 1)
			{
				var newPeaks = peaks.OrderBy(num => num);
				var peak1 = newPeaks.ElementAt(newPeaks.Count()-1);
				var peak2 = newPeaks.ElementAt(newPeaks.Count() - 2);
				var bodyHeight = Math.Abs(peak2 - peak1) + PeakWidth(histogram, peak1) + PeakWidth(histogram, peak2);
				Console.WriteLine("\n\t{0} Body Height", bodyHeight);
			}
			//int[] lows = GetPeaks(histogram, textLine, false);
			//Console.WriteLine("\tFound {0} low(s)", lows.Length);
			//for (int i = 0; i < lows.Length; i++) Console.WriteLine("\t\tLow #{0:D2}: {1} Transitions", i + 1, Transitions(pm, lows[i]));
			// get width of font (distance between peaks +)

			// print histogram of line
			
			//PrintTextLineHistogram(histogram, textLine, numberOfLines, lineLength);
		}

		private static void PrintTextLineHistogram(int[] histogram, TextLine textLine, int numberOfLines, int lineLength)
		{			
			if (numberOfLines > textLine.Height || numberOfLines == 0) numberOfLines = textLine.Height;
			int[] lineInfo = new int[numberOfLines];
			int size = textLine.Height / numberOfLines;
			for (int i = textLine.Start; i < textLine.End; i += size)
			{
				int avg = Avarage(histogram, i, i + size);
				PrintStars(avg, lineLength, histogram.Max());				
			}

			Console.WriteLine("press any key to continue ...");
			Console.ReadKey();
		}

		private static int Avarage(int[] histogram, int start, int end)
		{
			int sum = 0;
			for (int i = start; i < end; i++) sum += histogram[i];
			sum /= end - start;
			return sum;
		}

		private static void PrintStars(int value, int lineLength, int maxHistogramValue)
		{
			value = (int) ((double)value*lineLength/(double)maxHistogramValue);
			for (int i = 0; i < value; i++) Console.Write("*");
			Console.WriteLine();
		}

		private static int PeakWidth(int[] histogram, int peak)
		{
			int width = 1;
			int idx;
			double factor = (2.0 / 3.0) * histogram[peak];
			idx = peak+1;
			while (idx < histogram.Length - 1 && histogram[idx] > factor) { width++; idx++; }
			idx = peak - 1;
			while (idx > 0 && histogram[idx] > factor) { width++; idx--; }
			return width;
		}

		private static int[] GetPeaks(int[] histogram, TextLine textLine, bool findMax)
		{
			List<int> peaks = new List<int>();

			for (int i = textLine.Start; i <= textLine.End; i++)
			{
				if (LocalMinMax(histogram, i, findMax)) peaks.Add(i);
			}

			return peaks.ToArray();
		}

		private static bool LocalMinMax(int[] histogram, int row, bool findMax)
		{
			int steps = 6;
			var h = histogram;
			bool[] minus = new bool[steps];
			bool[] plus = new bool[steps];
			for (int i = 0; i < steps; i++)
			{
				minus[i] = plus[i] = true;
				if (row > i) minus[i] = CompareMinMax(h[row], h[row - (i + 1)], findMax);
				if (row + i + 1 < histogram.Length - 1) plus[i] = CompareMinMax(h[row], h[row + i + 1], findMax);
			}
			foreach (bool b in minus) if (!b) return false;
			foreach (bool b in plus) if (!b) return false;
			return true;
		}
		private static bool CompareMinMax(int n1, int n2, bool findMax)
		{
			if (findMax)
				return n1 > n2;
			return n2 > n1;
		}
		static int Transitions(PixelMatrix pm, int line)
		{
			int trans = 0;
			int y = line;
			bool last = pm[0, y];

			for (int x = 1; x < pm.Width; x++)
			{
				if (pm[x, y] == last) continue;
				last = pm[x, y];
				trans++;
			}
			return trans;
		}
	}
	public class PixelMatrix
	{
		public int Height { get { return _pixelMatrix[0].Length; } }
		public int Width { get { return _pixelMatrix.Length; } }
		bool[][] _pixelMatrix;
		public bool this[int x, int y] { get { return _pixelMatrix[x][y]; } set { _pixelMatrix[x][y] = value; } }
		public PixelMatrix(int width, int height)
		{ 
			_pixelMatrix=new bool[width][];
			for (int i = 0; i < width; i++) _pixelMatrix[i] = new bool[height];
			//for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) _pixelMatrix[x][y] = false;
		}
	}
}
